/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using Xceed.Silverlight.Data.Stats;

namespace Xceed.Silverlight.Data
{
  public abstract class AsyncSqlDataSourceProvider : AsyncDataSourceProvider
  {
    #region Static Fields

    private static readonly Guid ExecuteStatQueryCallId = DataUtils.CreateCallId();

    #endregion

    #region Constructor

    protected AsyncSqlDataSourceProvider()
    {
    }

    #endregion

    #region CanSkip Property

    public override bool CanSkip
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilter Property

    public sealed override bool CanFilter
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region CanFilterGroup Property

    public sealed override bool CanFilterGroup
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJump Property

    public sealed override bool CanJump
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanJumpToItem Property

    public sealed override bool CanJumpToItem
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCount Property

    public override bool CanCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanGroupCount Property

    public sealed override bool CanGroupCount
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region CanCalculateStats Property

    public override bool CanCalculateStats
    {
      get
      {
        return this.CanCount;
      }
    }

    #endregion

    #region CanFilterPredicate Internal Property

    internal override bool CanFilterPredicate
    {
      get
      {
        return false;
      }
    }

    #endregion

    protected abstract SqlFilterExpressionFormatter GetSqlQueryBuilder( FilterExpressionBase expression );

    protected abstract IAsyncResult BeginCount(
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract CountQueryResult EndCount( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetData(
      int take,
      long skip,
      IEnumerable<SortDescription> sortOrder,
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract DataQueryResult EndGetData( IAsyncResult asyncResult );

    protected abstract IAsyncResult BeginGetStatResult(
      StatFunction statFunction,
      SqlFilterExpressionResult filter,
      AsyncCallback callback,
      object state );

    protected abstract StatQueryResult EndGetStatResult( IAsyncResult asyncResult );

    protected sealed override IAsyncResult BeginExecuteCountQuery(
      CountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      SqlFilterExpressionResult filter = this.GetSqlFilterExpression( queryParameters.Filter );

      return this.BeginCount( filter, callback, state );
    }

    protected sealed override CountQueryResult EndExecuteCountQuery( IAsyncResult asyncResult )
    {
      return this.EndCount( asyncResult );
    }

    protected sealed override IAsyncResult BeginExecuteDataQuery(
      DataQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      IEnumerable<SortDescription> sortOrder = ( queryParameters.Sort != null )
                                                 ? queryParameters.Sort.SortDescriptions
                                                 : Enumerable.Empty<SortDescription>();
      long skip = ( queryParameters.Skip != null ) ? queryParameters.Skip.Count : 0;
      int take = ( queryParameters.Take != null ) ? queryParameters.Take.Count : 0;
      SqlFilterExpressionResult filter = this.GetSqlFilterExpression( queryParameters.Filter );

      return this.BeginGetData( take, skip, sortOrder, filter, callback, state );
    }

    protected sealed override DataQueryResult EndExecuteDataQuery( IAsyncResult asyncResult )
    {
      return this.EndGetData( asyncResult );
    }

    protected sealed override IAsyncResult BeginExecuteGroupCountQuery(
      GroupCountQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      throw new NotSupportedException();
    }

    protected sealed override GroupCountQueryResult EndExecuteGroupCountQuery( IAsyncResult asyncResult )
    {
      throw new NotSupportedException();
    }

    protected sealed override IAsyncResult BeginExecuteStatQuery(
      StatQueryParameters queryParameters,
      AsyncCallback callback,
      object state )
    {
      StatFunction statFunction = queryParameters.Function.StatFunction;
      CountFunction countFunction = queryParameters.Function.StatFunction as CountFunction;
      FilterRequest filterRequest = queryParameters.Filter;
      SqlFilterExpressionResult filter;

      // Let the derived class handled the StatFunction.
      if( ( countFunction == null ) || ( !this.CanCount ) )
      {
        filter = this.GetSqlFilterExpression( filterRequest );

        return this.BeginGetStatResult( statFunction, filter, callback, state );
      }

      // The CountFunction may be handled by a call to BeginCount.
      FilterExpressionBase filterExpression = filterRequest.Expression;
      string propertyName = countFunction.GetSourcePropertyNames().Single();
      bool isNullableType = this.GetElementMemberType( propertyName ).IsNullableType();

      // Modify the filter expression to take care of the null value.
      if( isNullableType )
      {
        if( countFunction.IncludeNullValues )
        {
          FilterExpression includeNull = new FilterExpression( propertyName, FilterOperator.Equal, null );

          if( filterExpression != null )
          {
            filterExpression = new OrFilterExpression( includeNull, filterExpression );
          }
          else
          {
            filterExpression = includeNull;
          }
        }
        else
        {
          FilterExpression excludeNull = new FilterExpression( propertyName, FilterOperator.NotEqual, null );

          if( filterExpression != null )
          {
            filterExpression = new AndFilterExpression( excludeNull, filterExpression );
          }
          else
          {
            filterExpression = excludeNull;
          }
        }
      }

      filter = this.GetSqlFilterExpression( filterExpression );

      AsyncResult asyncResult = new AsyncResult(
                                  AsyncSqlDataSourceProvider.ExecuteStatQueryCallId,
                                  callback,
                                  state );
      asyncResult.Start(
        ( cb, st ) => this.BeginCount( filter, cb, st ),
        null );

      return asyncResult;
    }

    protected sealed override StatQueryResult EndExecuteStatQuery( IAsyncResult asyncResult )
    {
      if( asyncResult.UseCallId( AsyncSqlDataSourceProvider.ExecuteStatQueryCallId ) )
      {
        CountQueryResult countQueryResult = this.EndCount( asyncResult.GetLastAsyncResult<IAsyncResult>() );
        StatResult statResult = new StatResult( countQueryResult.Count );

        return new StatQueryResult( statResult );
      }
      else
      {
        return this.EndGetStatResult( asyncResult );
      }
    }

    protected override IEnumerable<Type> GetSupportedStatFunctions( Type dataType )
    {
      // Check if we can replace the CountFunction with a call to BeginCount.
      if( this.CanCount )
      {
        bool mayCount = !dataType.IsNullableType();
        if( ( !mayCount ) && ( this.CanFilter ) )
        {
          // Since the CountFunction may require null values inclusion or exclusion, we need
          // to be able set a filter for null or non-null values.
          List<FilterOperator> filterOperators = this.GetSupportedFilterOperators( dataType ).ToList();

          mayCount = ( filterOperators.Contains( FilterOperator.Equal ) )
                  && ( filterOperators.Contains( FilterOperator.NotEqual ) );
        }

        if( mayCount )
          yield return typeof( CountFunction );
      }

      yield break;
    }

    private SqlFilterExpressionResult GetSqlFilterExpression( FilterRequest request )
    {
      if( request == null )
        return null;

      return this.GetSqlFilterExpression( request.Expression );
    }

    private SqlFilterExpressionResult GetSqlFilterExpression( FilterExpressionBase expression )
    {
      if( expression == null )
        return null;

      SqlFilterExpressionFormatter converter = this.GetSqlQueryBuilder( expression );
      Debug.Assert( converter != null );

      return converter.GetResult();
    }
  }
}
